実際に作ってみる・ver0.10〜0.09 まで
▼ ver0.10 今回のバージョンアップでは自機ショットと敵キャラの当たり判定を付けます。 つまりショットで敵を倒せるようにするわけですね。ようやくゲームらしくなっ てきたところでバージョンは 0.10 とします。 当たり判定は自機ショットと敵キャラの座標を比較して行います。当たり判定 は自機ショット×敵キャラの数だけ必要で、これらの座標の比較はX方向2回+ Y方向2回の4回必要となります。この事からもわかる通り、当たり判定は膨大 な数の数値比較が必要となり、シューティングゲームの中で最も重い処理と言え るでしょう。そのため、いかに当たり判定を高速化するかがプログラミングテク ニックの見せどころです。 …と言っても今回はまだ ver0.10 という事で素直に作ってみることにします。 プログラムは以下のようになります。 ---- while (敵キャラのワーク != NULL) { while (自機ショットのワーク != NULL) { if (敵キャラの座標と自機ショットの座標を比較して当たったか?){ 当たりの処理(); } 次のショットのワークへ; } 次の敵キャラのワークへ; } ---- 当たり判定は独立したルーチンで行った方が機能の分離という点からは好まし いのですが、ここは特に速度が要求される部分なので移動ルーチン EnemyMove() の中で移動処理の直後に行っています。 ---- while (敵キャラのワーク != NULL) { 各キャラの移動ルーチンへ(); if (敵キャラの座標と自機ショットの座標を比較して当たったか?){ : ---- また、実際に当たった時の処理は敵の耐久力を減らし、ショットを消す準備を するだけです。弾が当たった時の処理は敵/ショットの種類によって異なるので、 ここではワークの数値を変化させるだけで具体的な処理ルーチンは各移動ルーチ ンに任せています。 ● 実行=非対応メニューです ◎ main.c ◎ enemy.c ▼ ver0.11 爆発パターンの処理を追加しました。それに伴い effect.h / effect.c を追 加しています。この内容も shot.c や enemy.c とほぼ同内容ですので理解は用 意でしょう。 ● 実行=非対応メニューです ◎ effect.h ◎ effect.c enemy.c 内で当たり判定を行い、敵が爆発した時は、 ---- /* 耐久力が0以下なら消去 */ if (p->hp <= 0) { EffectAlloc (EFFECT_EXPLZAKO, 0, p->x, p->y); /* 爆発パターンを出現させる */ return (-1); } ---- のように爆発パターンを発生させています。 ▼ ver0.12 敵弾の処理を追加しました。それに伴い eshot.h / eshot.c を追加していま す。この内容も shot.c や enemy.c や effect.h とほぼ同内容です。この敵弾 は自機を狙ってきますが、この時方向を決定するために要素研究2・自機サーチ と ATAN テーブルで解説した方法を使用しています。 ● 実行=非対応メニューです ◎ eshot.h ◎ eshot.c ◎ enemy.c ◎ psearch.c ---- angle = psearch (p->x, p->y); /* 敵から見た自機の方向を unsigned char で返す */ EshotAlloc (0, p->x, p->y, 10, angle); /* 弾を撃つ */ ---- enemy.c 内のこの2行で自機サーチと敵弾発生を行っています。 ▼ ver0.13 自機と敵の当たり判定、自機と敵弾の当たり判定を追加しました。それぞれ enemy.c / eshot.c 内で、処理内容は自機ショットと敵の当たり判定とほぼ同内 容です。それに伴い player.c 内に自機が爆発した後に下から出てくる処理を追 加してあります。プログラム的にはどうという事の無い処理ですが、いきなり画 面上に自機が現れるよりも高級感?があって見栄えがする処理ですね。ゲームの 見栄えは案外こんなところで差が付くものです。 ● 実行=非対応メニューです ◎ enemy.c ◎ eshot.c ◎ player.c まだ自機爆発直後の無敵処理がないため、登場直後にまた敵に当たって死んで しまう事があります。無敵処理がないとどれだけ遊びにくいかを実感してみて下 さい。 ▼ ver0.14 敵キャラの種類が増えるにつれ enemy.c が肥大する事が予想されるため、各 敵キャラのルーチンを FuncEnemy¥ 以下に移動しました。zakoa.c / zakob.c の ように1ソースリスト1種類という判り易い分類になっています。各ルーチンの インターフェースは EnemyAllocZakoA() で出現、垂直同期ごとに EnemyMoveZakoA() を実行、消去する時に EnemyFreeZakoA() を呼ぶ、という形 にしてあります。敵キャラごとにこの中身を書き下ろせば敵キャラクターの完成 です。 ● 実行=非対応メニューです ◎ enemy.c ◎ FuncEnemy/zakoa.c ◎ makefile このようにソースリストの数が増えて来た場合、種類ごとにディレクトリを分 けて開発を行うのは有効な手段です。そのためには makefile の書き方に少し工 夫が必要になります。 ---- makefile ---- vpath %.c ./;FuncEnemy/;FuncEffect/; # .c ファイルはカレント又はここで指定したディレクトリに :(中略) zakoa.o: FuncEnemy/zakoa.c main.h player.h enemy.h effect.h psearch.h ------------------ GNU make のコマンド vpath を使ってソースリストの検索パスを指定する必要 があります。無指定時はカレントディレクトリのみを検索しますが、上記のよう に指定すると拡張子が .c のファイル(%.c)をカレントディレクトリ(.)、 FuncEnemy ディレクトリ、FuncEffect ディレクトリから検索するようになりま す。 zakoa.o 行はソースリストのファイル名とヘッダのファイル名を指定します。 実際の makefile 中では ---- makefile ---- zakoa.o: FuncEnemy/zakoa.c FuncEnemy/../main.h FuncEnemy/../player.h FuncEnemy/../enemy.h FuncEnemy/../effect.h FuncEnemy/../psearch.h (実際には1行) ------------------ のようになっていますが、これは newmake.x(電脳倶楽部135号掲載の makefile 作成/更新ツール)を使用しているためです。FuncEnemy/../main.h は main.h と等価ですので気になさらずに。 ▼ ver0.20 色々追加しました。 ● 実行=非対応メニューです まずは背景グラフィックの処理を追加しました。処理的には単にグラフィック 画面に画像を転送しているだけなのですが、ちょっとだけ凝った事をしています。 1つはBGパターン形式のデータをその場で変換して表示している事。これに より「てぺ」で書いた .SP 形式のパターンと .MAP 形式のマップデータをその まま使えるようにしています。.SP 形式は4ビット/1ドットの形式であるのに 対し、グラフィック画面256色モードは1バイト/1ドットと異なった形式です。 これをわざわざ変換せずにメモリ上に持って表示時に変換しているのは、 ・メモリの節約 グラフィック画面形式に変換してしまうと途端に倍のメモリが必要に なってしまう。 ・「てぺ」のデータがそのまま使える グラフィック担当の人にテータ形式だけ教えて渡しておけば、実際に 動かしながらグラフィックを作成することができる。データ変換や再コ ンパイルが必要だと、グラフィックを書く度にプログラマーにデータを 渡して変換/再コンパイルしなければならない。 前者は技術的な問題ですが、後者は分業をする上での工程管理的な問題です。 この「実際に動かしながらグラフィックを描ける」というのは意外に重要で、 「静止画ではではいいんだけど動かすと見辛い」とか「実際に組み込んで動かそ うとしたらデータの形式が間違っていた」というような問題を未然に防ぐことが できます。連絡不徹底により折角作ったデータが使えなくなるという事は双方に とって不幸な事ですから、絶対に避けなければいけません。そのために実際に動 かしてみる事はとても有効です。 また、この方法は開発期間短縮にも有効です。今回の例で言うと、自機が動か せるようになったら自機のグラフィックを、自機ショットが動かせるようになっ たら自機ショットのグラフィックを、というように五月雨式にグラフィック担当 者に仕事を発注する事ができます。もしこれが「プログラムが完成した後にグラ フィックをまとめて発注する」というような形式だと開発期間が長くなってしま います。 これを実現するための必要条件として「プログラムをとりあえず動かせる状態 に持っていく」という事が挙げられます。そのためにはプログラムの作成順序も 考えていく必要があります。この辺はプログラマーよりプロデューサーの仕事か もしれませんが、両者を一人(自分)で兼ねる場合は自分で考えなければなりま せん。この辺のスケジュール決定はRISCプロセッサーのパイプラインを詰め る作業にも似て、難しいのですが面白い所でもあります。 話がそれましたが元に戻して、グラフィック描画で凝っているもう1つの点と しては「1度に描かずに垂直同期64回分に分けて描画している」事です。グラフィッ ク画面への描画は結構重い処理で、1回で描かせようとすると処理落ちを起こし てスクロールがガタついたり止まったりしてしまいます。そこで今回は垂直同期 1回ごとに256x16ドットを表示し、それを64回繰り返すことによって256x512ドッ トの範囲にグラフィックを表示しています。 以上の処理を行う gvram.s はアセンブラで描かれたルーチンですが、gvram.h でプロトタイプ宣言を行うことにより、C言語から普通の関数と同じように呼び 出す事ができるようになっています。 ◎ gvram.s ◎ gvram.h 256x256ドット256色モードの画面2枚にそれぞれ256x512ドットのグラフィッ クを描画し、下にスクロールさせています。スクロール座標が512ドットを越え るとまた0に戻るようにしておけば無限ループ背景の完成です。 テキスト画面に文字表示を行う txfont.c を追加しました。2ページ使うこと により最大4色(うち1色は透明色なので実質3色)使用する事ができるように してあります。 ◎ txfont.c 敵キャラ出現処理を entry.c に追加しました。DAT¥STAGE.DAT を読み込んで データに従って EnemyAlloc() を呼び出します。STAGE.DAT は STAGE.S をアセ ンブルして .R 形式に変換して(.X 形式だと余分なヘッダ情報が付いてしまう ため)作りました。.dc.b でデータをずらずら並べただけです。『男弾』の時は ジョイスティックで的を配置するようなルーチンを作りかけたのですが (player.c にその痕跡が…)、結局、テキストエディタでガリガリ書いた方が 良いと思ったのでこうしています。 ◎ entry.c ◎ DAT/stage.s 音関係の処理を追加しました。以前解説した ZMCALL.S と音楽/効果音ファイ ル読み込み関係をまとめた SOUND.C を追加しました。音楽/効果音は SOUND.CNF で指定されたファイルを読み込むようにしています。バイナリ化して (HAS060.X v3.09+69 の .insert 機能を使用)ソースリスト中に直接埋め込ん でしまっても良いのですが、テキストファイルで指定する形式の方が思考錯誤し やすいため開発効率の観点からこの方法を採用しています。 ◎ zmcall.s ◎ sound.c PCM8A.X 常駐時に 68000 機で ver0.20 を動かすと、時折スプライトが点滅す ることがあります。これは XSP と PCM8A の間で割り込みが競合するためです。 これを回避するためには XSP に同梱の pcm8a_vsyncint_on () 関数を使用して 下さい(ver1.00 では使っています)。 自機ショットを今回のゲームデザインに合わせて貫通弾にしました。処理内容 的には自機ショットが敵に当たっても消去しないようにしただけです。 ▼ ver0.50 ● 実行=非対応メニューです プログラム技術的にはほとんど変わらないバージョンですが、細かいところを 追加しました。 ・自機ショットが敵に刺さるようにした ・得点関係の処理を追加 敵を倒した時の得点表示は EFFECT です。 ・自機が死んだ後に無敵時間を設けるようにした ・敵キャラを増やした このバージョンでゲームとしての骨格は固まったので、後は敵キャラの追加・ エントリー作成がメインとなります。 そして ver1.00 に続く…。 (ver1.00 は今月のゲームのコーナーにあります) (EOF)